#include "stdafx.h"

namespace cpputils
{

using namespace file_formatter_impl;

file_formatter::file_formatter(FILE* SetFile)
	: File(SetFile), Reading(true)
{
	Types.assign(numeric_limits<unsigned char>::max() + 1, alpha_char);
	set_blanks("\t\n ");
}

FILE* file_formatter::get_file() const
{
	return File;
}

void file_formatter::set_file(FILE* Val)
{
	File = Val;
}

bool file_formatter::is_reading() const
{
	return Reading;
}

void file_formatter::set_reading()
{
	Reading = true;
}

bool file_formatter::is_writing() const
{
	return !Reading;
}

void file_formatter::set_writing()
{
	Reading = false;
}

void file_formatter::set_blanks(const string& Val)
{
	for (int I = 0; I < Types.size(); I++)
		if (Types[I] == blank_char) Types[I] = alpha_char;
	for (int I = 0; I < (int)Val.length(); I++)
		Types[Val[I]] = blank_char;
}

void file_formatter::set_delimiters(const string& Val)
{
	for (int I = 0; I < Types.size(); I++)
		if (Types[I] == delimiter_char) Types[I] = alpha_char;
	for (int I = 0; I < (int)Val.length(); I++)
		Types[Val[I]] = delimiter_char;
}

bool file_formatter::is_alphas(const string& S)
{
	for (int I = 0; I < (int)S.length(); I++) if (!is_alpha(S[I])) return false;
	return true;
}

int file_formatter::peek_nonblank()
{
	int Ch;
	while (is_blank(Ch = fgetc(File)));
	if (Ch == EOF) check_error();
	else ungetc(Ch, File);
	return Ch;
}

bool file_formatter::eof()
{
	return peek_nonblank() == EOF;
}

void file_formatter::finish()
{
	if (!eof()) format_error("End-of-file expected");
}

file_formatter& file_formatter::operator<<(char Ch)
{
	assert(!is_alpha(Ch));
	if (Reading)
	{
		if (is_blank(Ch)) return *this;
		if (peek_nonblank() != Ch) format_error(string("Delimiter expected: ") + Ch);
		get();
	}
	else
		put(Ch);
	return *this;
}

file_formatter& file_formatter::operator<<(const char* S)
{
	expect_string(S, strlen(S));
	return *this;
}

file_formatter& file_formatter::operator<<(const string& S)
{
	expect_string(S.c_str(), S.length());
	return *this;
}

void file_formatter::error()
{
	if (Reading) throw runtime_error("Input error at position " + to_string((unsigned)ftell(File)));
	throw runtime_error("Output error at position " + to_string((unsigned)ftell(File)));
}

void file_formatter::check_error()
{
	if (ferror(File) != 0) error();
}

void file_formatter::format_error(const string& Msg)
{
	assert(Reading);
	check_error();
	throw runtime_error("Input format error at position " + to_string((unsigned)ftell(File)) + ": " + Msg);
}

void file_formatter::expect_string(const char* S, int Len)
{
	if (Reading)
		for (int I = 0; I < Len;)
			if (!is_alpha(S[I])) *this << S[I++];
			else
			{
				int J = I + 1;
				while (J < Len && is_alpha(S[J])) J++;
				peek_nonblank();
				for (int K = I; K < J; K++)
					if (fgetc(File) != S[K])
						format_error("Token expected: " + string(S + I, S + J));
				I = J;
			}
	else
		if ((int)fwrite(S, 1, Len, File) < Len) error();
}

void format(file_formatter& Fmt, char& Ch)
{
	if (Fmt.is_reading())
	{
		Ch = Fmt.peek_nonblank();
		if (!Fmt.is_delimiter(Ch)) Fmt.format_error("Delimiter expected");
		Fmt.get();
	}
	else
	{
		assert(Fmt.is_delimiter(Ch));
		Fmt.put(Ch);
	}
}

void format(file_formatter& Fmt, string& S)
{
	FILE* File = Fmt.get_file();
	if (Fmt.is_reading())
	{
		S.clear();
		Fmt.peek_nonblank();
		int Ch;
		while (Fmt.is_alpha(Ch = fgetc(File))) S += Ch;
		if (Ch == EOF) Fmt.check_error();
		else ungetc(Ch, File);
		if (S.empty()) Fmt.format_error("Token expected");
	}
	else
	{
		assert(Fmt.is_alphas(S));
		if (fwrite(S.c_str(), 1, S.length(), File) < S.length()) Fmt.error();
	}
}

void format(file_formatter& Fmt, int& V)
{
	return format_int(Fmt, V, "%d");
}

void format(file_formatter& Fmt, unsigned int& V)
{
	return format_int(Fmt, V, "%u");
}

void format(file_formatter& Fmt, short& V)
{
	return format_int(Fmt, V, "%hd");
}

void format(file_formatter& Fmt, unsigned short& V)
{
	return format_int(Fmt, V, "%hu");
}

void format(file_formatter& Fmt, long & V)
{
	return format_int(Fmt, V, "%ld");
}

void format(file_formatter& Fmt, unsigned long& V)
{
	return format_int(Fmt, V, "%lu");
}

void format(file_formatter& Fmt, long long& V)
{
	return format_int(Fmt, V, "%lld");
}

void format(file_formatter& Fmt, unsigned long long& V)
{
	return format_int(Fmt, V, "%llu");
}

void format(file_formatter& Fmt, float& V)
{
	return format_float(Fmt, V, "%f");
}

void format(file_formatter& Fmt, double& V)
{
	return format_float(Fmt, V, "%lf");
}

namespace file_formatter_impl
{

	inline bool is_std_blank(int Ch)
	{
		return Ch == ' ' || Ch == '\t' || Ch == '\n';
	}

	template<class T>
	void format_int(file_formatter& Fmt, T& V, const char* Type)
	{
		if (Fmt.is_reading())
		{
			if (is_std_blank(Fmt.peek_nonblank()) || fscanf(Fmt.get_file(), Type, &V) <= 0)
				Fmt.format_error("Integer value expected");
		}
		else
			if (fprintf(Fmt.get_file(), Type, V) < 0) Fmt.error();
	}

	template<class T>
	void format_float(file_formatter& Fmt, T& V, const char* Type)
	{
		if (Fmt.is_reading())
		{
			if (is_std_blank(Fmt.peek_nonblank()) || fscanf(Fmt.get_file(), Type, &V) <= 0)
				Fmt.format_error("Numerical value expected");
		}
		else
			if (fprintf(Fmt.get_file(), "%.*g", numeric_limits<T>::digits10, V) < 0) Fmt.error();
	}

}

}
